/*
 * @Author      : ZhouQiJun
 * @Date        : 2025-07-16 11:54:03
 * @LastEditors : ZhouQiJun
 * @LastEditTime: 2025-08-20 23:43:51
 * @Description : ol 图层工厂
 */
import { Abstract, type ILayerFactory } from '@ol/api'
import { type GisRef } from '@ol/define'
import { Projections, Transforms, valueOf } from '@ol/utils'
import { isNil } from 'es-toolkit'

import type { ILayerOptions, IWMTSLayerOptions, LayerType, ProjCode } from '../../types'
import * as olExtent from 'ol/extent.js'
import type BaseLayer from 'ol/layer/Base'
import TileLayer from 'ol/layer/Tile.js'
import WMSSource from 'ol/source/TileWMS.js'
import WMTSSource from 'ol/source/WMTS.js'
import XYZSource from 'ol/source/XYZ.js'
import WMTSTileGrid from 'ol/tilegrid/WMTS.js'

type MapConfig = {
  'tile.header.include': '*' | ['wms', 'wmts'] | null
  'layer.tile.headers': Record<string, string> | (() => string) | null
}

// NOTE 提取成默认的地图配置
const options: MapConfig = {
  /**
   * 配置图层瓦片请求时加 header 的图层类型，可以为：
   * 1. "*": 表示任意图层，即所有图层都需要；
   * 2. string[]: 仅在列表中的图层类型需要（如：["wms","wmts"]）。
   */
  'tile.header.include': null,

  /**
   * 图层瓦片具体的 header，可以为：
   * 1. object: 即固定 header；
   * 2. function: 用一个函数来动态获取 header。
   */
  'layer.tile.headers': null,
}

const include = options['tile.header.include']

/**
 * ol的图层工厂实现。
 * >>
 * 某些图片图层（如 wms、wmts ）需要对图片请求进行验证（为图片增加header），
 * 对哪种图层进行验证和验证信息由{@link options}相关配置提供。
 *
 * 图层支持多域名，由{@link ILayerOptions.subdomains}提供，由于ol不直接支持多域名，因此域名会转换为对应的url。
 *
 * @author    ZhouQiJun
 * @version   0.1.0
 */
export default class OlLayerFactory extends Abstract implements ILayerFactory {
  // 调用父类的构造函数
  //constructor(map: GisRef) {
  //  super(map)
  //}
  // FIXME 类型如何定义？
  //public create(options: ILayerOptions | ILayerOptions[]): BaseLayer | BaseLayer[] {
  // @ts-ignore
  public create(options: ILayerOptions | ILayerOptions[]): unknown {
    if (Array.isArray(options)) {
      // FIXME 类型如何定义？
      // @ts-ignore
      return options.map((item) => factories[item.type](item, this.map))
    }
    return factories[options.type](options, this.gis)
  }
}

function createUrls(config: ILayerOptions) {
  //return config.subdomains?.map(domain => config.url.replace('{s}', String(domain)))
}

function toExtent(config: ILayerOptions, gis: GisRef) {
  if (isNil(gis)) return undefined
  return config.extent
    ? Transforms.extent(config.extent, config.projection as ProjCode, gis.projection)
    : undefined
}

/**
 * 层级顺序1：详细底图，2：发布图层（如：雨情等），3：行政驻地图层
 * @param zIndex 层级
 * @returns
 */
function addZIndex(zIndex: number = 2) {
  return zIndex ? zIndex : 2
}

type Factories = {
  // eslint-disable-next-line no-unused-vars
  [key in LayerType]: (options: ILayerOptions, gis: GisRef) => BaseLayer | BaseLayer[]
}

const factories: Factories = {
  /**
   * 地图瓦片服务
   * @param config
   * @param gis 地图对象
   */
  wmts(config: IWMTSLayerOptions, gis: GisRef): BaseLayer {
    let layer: BaseLayer
    const crossOrigin = isNil(config.crossOrigin) ? '' : config.crossOrigin
    const { layer: _layerName, name, id } = config
    const layerName = _layerName || name
    const className = convertClassName(config, 'wmts')
    if (config.location === 'xyz') {
      layer = new TileLayer({
        zIndex: config.zIndex,
        opacity: config.opacity,
        minZoom: config.minZoom,
        maxZoom: config.maxZoom,
        extent: toExtent(config, gis),
        className: `${className} tile-layer xyz-source`,
        source: new XYZSource({
          url: config.url,
          //@ts-ignore
          urls: createUrls(config),
          tileSize: config.tileSize,
          //@ts-ignore
          tileUrlFunction: config.decoder,
          projection: config.projection,
          crossOrigin,
          wrapX: config.wrapX || true,
        }),
      })
    } else if (config.location === 'bbox') {
      layer = new TileLayer({
        zIndex: config.zIndex,
        opacity: config.opacity,
        minZoom: config.minZoom,
        maxZoom: config.maxZoom,
        extent: toExtent(config, gis),
        className: `${className} tile-layer wms-source`,
        source: new WMSSource({
          url: config.url,
          //@ts-ignore
          urls: createUrls(config),
          projection: config.projection,
          crossOrigin: config.crossOrigin || crossOrigin,
          wrapX: config.wrapX || true,
          params: {
            LAYERS: config.layer,
            STYLES: config.style,
            VERSION: config.version || '1.0.0',
            WIDTH: config.tileSize?.[0],
            HEIGHT: config.tileSize?.[1],
            FORMAT: config.format || 'image/png',
            // TODO
            //viewparams
            TILED: true,
            ...config.custom,
          },
        }),
      })
    } else {
      // Default WMTS configuration
      console.log({ config }, 'zqj log {wmts config}')

      const projection = Projections.get(config.projection || 'EPSG:3857')
      const extent = projection!.getExtent()
      const size = olExtent.getWidth(extent) / 256

      let resolutions: number[] = []
      if (config.resolutions && config.resolutions.length > 2) {
        resolutions = config.resolutions
      }
      let matrixIds: string[] = []
      if (config.matrixIds && config.matrixIds.length > 2) {
        matrixIds = config.matrixIds
      }

      if (resolutions.length === 0) {
        const range = config.resolutions || [0, 19]
        for (let s = range[0], i = 0; i <= range[1]; ++i) {
          resolutions[i] = size / Math.pow(2, s + i)
        }
        // console.log(resolutions)
      }
      if (matrixIds.length === 0) {
        const range = (config.matrixIds || [0, 19]) as number[]
        for (let s = range[0], i = 0; i <= range[1]; ++i) {
          matrixIds[i] = (config.matrixPrefix || '') + (s + i)
        }
        console.log({ matrixIds }, 'zqj log {matrixIds}')
      }
      console.log({ matrixIds }, '{matrixIds}')

      layer = new TileLayer({
        zIndex: config.zIndex,
        opacity: config.opacity,
        minZoom: config.minZoom,
        maxZoom: config.maxZoom,
        extent: toExtent(config, gis),
        className: `${className} tile-layer wmts-source`,
        source: new WMTSSource({
          projection,
          url: config.url,
          // @ts-ignore
          urls: createUrls(config),
          /**
           * 图层名称
           */
          layer: config.layer as string,
          style: config.style ?? 'default',
          format: config.format,
          matrixSet: config.matrixSet as string,
          crossOrigin: config.crossOrigin || crossOrigin,
          wrapX: config.wrapX || true,
          tileGrid: new WMTSTileGrid({
            origin: olExtent.getTopLeft(extent), // Use olExtent directly
            // @ts-ignore
            origins: config.origins,
            extent,
            matrixIds,
            resolutions,
          }),
        }),
      })
      // @ts-ignore
      const headers = valueOf<Record<string, string>>('gis.layer.headers', options)
      if (headers && include && (include === '*' || include.includes('wms'))) {
        // FIXME 如何定义类型
        // @ts-ignore
        const source = layer.getSource()
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        source.setTileLoadFunction((tile: any, src: string) => {
          const xhr = new XMLHttpRequest()
          xhr.open('get', src)
          xhr.responseType = 'blob'
          xhr.onloadend = () => {
            if (xhr.response) {
              const image = tile.getImage()
              image.src = URL.createObjectURL(xhr.response)
              image.onload = () => URL.revokeObjectURL(image.src)
            }
          }
          // 瓦片请求添加 http header
          Object.keys(headers).forEach((name) => xhr.setRequestHeader(name, headers[name]))
          xhr.send()
        })
      }
    }
    // 设置 id 和 name 方便查询
    layer.set('id', id || layerName, true)
    layer.set('name', layerName, true)
    return layer
  },
}

function convertClassName({ className: _className, layerName, name, zIndex }: ILayerOptions, type: string) {
  const classList = ((_className as string) ?? '').split(' ').map((item) => item.trim())
  const className = [
    ...new Set([...classList, layerName, name, `z-index-is-${zIndex}`, 'ol-layer', `${type}-layer`]),
  ]
    .filter(Boolean)
    .join(' ')
  return className
}
